Hey there.
In this lecture we're going to be creating a combat knife melee weapon that all our players can use
when they spawn into the game to go ahead and fight and kill any zombies that come their way.
He gets started, what we're going to go ahead and do is open up our avatar tab and create a new rig
using the rig builder, and we'll do a block avatar in our 15.
And then we're going to head over to server storage, and we're going to grab our knife out of the tools
folder.
And we're going to change the parent equal to our rig right here.
And then I believe it got parented to one of the body parts.
So we'll move it as a child of our rig.
There we go.
And as you can see, the rig should now be holding the knife in the correct position.
Now using this rig, we want to go ahead and create several animations for our knife.
We want to create an animation for holding our knife, and we're going to want to create several animations
for swinging our knife around.
So we can go ahead and select our avatar, and then we'll open up our animation editor.
Actually looks like we'll have to reselect our rig again.
We can create a new animation clip, and this is where you're going to start animating for your knife.
You'll want to have your animation be exactly one second in length.
And that's because we're going to be adjusting how long this animation plays through our scripts.
So by setting it at a one second duration, we are able to multiply that value by a percentage to determine
how many seconds we would like this animation to last.
So you can go ahead and start off by creating a starting pose for your knife.
But once you have the starting pose for your knife down, what you want to make sure is that this button
right here is checked.
So we're looping the animation, and you also want to make sure you set the animation priority to movement.
Once you've done that, you can go ahead and publish this animation to Roblox and give it a name of
whatever you'd like.
And then once you submit it and publish it to the game, there will be an ID that will pop up that you're
going to copy.
And we're going to be using these IDs for later.
Now, I've already uploaded this animation, so I'm not going to re-upload it again, but we're going
to move on and create the next series of our animations, which should be the swinging animations.
And your swinging animations should start, and they should end with the same key frames that match
your equip animation.
So I can copy this and put this at the end of my track as well.
So now we have the same holding position at the beginning and then the end of our animation.
And then throughout all of these different points on our track is where we can go ahead and start animating
the different swinging animations for when we click and use our knife.
Okay, so I have made three attack animations.
I've just called it attack one, attack two and attack three.
So this is my first attack animation and then this is my second attack animation.
And then this is my third attack animation.
And you can see that all of my attack animations start and end with the same key frames as my equip
animation.
And then between those two frames is where I've animated the attack.
I've also added a little bit of customization to these different key frames, such as.
I've selected the easing style to be cubic, and I've changed the easing direction to be in and out.
But of course you can go ahead and manipulate the easing style and the easing direction for your keyframes
however you would like.
Or if you would like to use these animations that I've created, then I'll have this rig attached to
the lecture that you can go ahead and load these different animations and then publish them to Roblox.
And then once you publish them and get all of those IDs, we can go ahead and start scripting.
All right, so now what we could do is inside of our knife tool, we can go ahead and add a new module
script.
And this module script is just going to store a table of all the different properties we would like
to have for our knife.
I'll just rename this module script to properties.
And then inside of my module, I can just go ahead and create a variable.
I'll just call it self and it's going to be a table.
And then we'll make sure to return self at the end of the module script.
And inside of this table is where we will have all of the properties for our knife.
For example we can create a key, we can call it damage.
And this will be the base damage for how much we want to deal to zombies in our game.
And we can set it to a value of like 95.
And then if we would like to implement a system where every time a player swings and they hit a zombie,
there is a chance for them to get a critical hit, then we can go ahead and create a key value pair.
I'll call it critical hit chance and we can set it to a float to represent like the percentage chance
that we'll have of getting a critical hit.
And we could do like 0.15.
So that would be like a 15% chance.
And if we do get a critical hit chance, then what we can do is define a table that can store the minimum
and the maximum value that we should multiply our damage by to give them a critical hit.
So we could call this our critical hit multiplier.
And inside of here we can have a minimum and a maximum key and set it equal to some kind of value.
So the minimum we could do is 1.3 times the base damage.
And then the max we could do like 1.9 times the base damage.
So this gives them a little bit more opportunity to deal extra damage to zombies that are fighting with
using their knife.
And then another thing we want to define is a cooldown between each time the player swings their knife.
So when the player swings their knife, the animation is going to play.
And then we're going to wait until the animation is finished playing, and then we can apply some sort
of cooldown before they can go ahead and swing again.
Now, we don't want this swing cooldown to be too long, so we could set it to a value of like 0.1 seconds.
Next we can have a section in our properties for animations.
And inside of here is where we're going to want to use those IDs that we copied for our animations and
store them in this module script.
So we could have a property, I'll call it swing animations.
And this will just be an array that stores all of the different IDs for the swinging animations.
So I've just gone ahead and copied each of the IDs for my swinging animations for my attack one, two
and three animations.
You'll want to do the exact same thing.
Copy those IDs and make sure you place this asset ID colon and then slash slash prefix before it.
And then you can put your ID here.
And these are my three attack or swinging animations.
And then I'll go ahead and define a key for my equip animation.
So I'll call this my equip animation.
And again here's where you're going to want to paste the ID for your equip animation.
So I'll paste mine in right here.
And then let me make sure to put a comma afterwards.
So that way we're not getting that error.
And then the last thing we want to define for our animations is specifically how long we want our swing
animations to last, because if you remember, each one of our animations lasts for one second.
However, it looks kind of slow when you have to swing over the entire duration of one second in our
animation.
So here is where we can define the animation duration for our swing animations.
And we could do something like 0.35 seconds.
The next thing we can go ahead and define are some sounds for our knife.
So we want to have a swing sound and this will be the swing sound ID.
And then we can also define the hit sound ID.
And this is where we can go ahead and open up our toolbox.
And we can go to the audio section and try to find any good audio sounds for swinging and hitting something
with our knife.
So I found two good audios to use for swinging and hitting.
If you would like to use the exact same audios as me, you can go ahead and copy these IDs and paste
them inside of your module script, because these are universal sounds that can be used across any game.
Now, another thing I want to go ahead and do is I want to manipulate the sounds so they don't sound
the exact same every time we're swinging our tool.
Instead, we want to be able to pitch the sound up or down randomly by using a pitch shift instance.
And to show an example of this, I'm just going to create a new sound instance inside of my knife.
And then I'll add and then I'll copy one of these sound IDs.
For example, we could do the swing sound.
I'll copy this.
And then I can place it inside of the sound ID property.
So then you can go ahead and listen to what the swing sound is.
That's our swing sound.
And then I want to be able to add a pitch shift sound effect, which is going to shift the octave of
the sound by default.
If we set it to one, that means our sound is going to sound the exact same.
But I can go ahead and pitch the sound up or down using this octave property.
For example, if I want the sound to have a lower pitch, I could do something like 0.8.
And now if you listen, it has a different sound, and then I can also pitch it up higher to like 1.2.
And there you go.
It sounds different again.
So we want to basically pitch or change the pitch of our sound each time we swing our knife.
So it's not the exact same sound playing over and over again.
And we want to do that for both of our sounds.
So I'll create a property, I'll call it pitch shift octaves to define a minimum and a maximum value
for the pitch.
So the minimum we could do like 0.8.
And then for the max we could do 1.2.
And then I want to go ahead and define the properties that all of the sounds are going to be using.
So for example, we want to fill this up with different sound properties.
So for example, if we look in the sound we want to go ahead and change the roll off max distance and
the roll off minimum distance as well as the roll off mode and the volume as well.
So inside of here we can go ahead and define like roll off max distance.
And we could do a distance of up to 40 studs away.
So anybody further than 40 studs away is not going to hear our knife swing or hit anything.
And then we'll do roll off min distance.
We'll set it to something like 15 studs.
The roll off mode we want to set to the enum dot roll off mode dot.
We could do inverse tapered which sounds the best in my opinion.
And then the volume we can go ahead and set to something like 0.25.
And that's going to be the properties or the sound properties for these two sound IDs.
Now that we have all of the properties set up for our knife, we're going to go ahead and create a new
local script as well as a new server script inside of our knife.
I'll rename the local script to our knife listener.
And I'll rename the server script to our knife handler.
Inside of the local script.
We're going to be using that exact same common template that we've been using before.
And inside of our local script, we can go ahead and create a variable called tool to reference the
tool itself, which is simply script dot parent.
And since this is a local script, we can also grab the player as well, so we'll get the player service.
And then we can grab the player from players, local player, and then down inside of our handler section,
we want to go ahead and listen to when our tool is equipped, when it's unequipped and when it's activated.
So when our tool is equipped, this is where we would want to go ahead and play that equip animation.
When our tool is unequipped, we want to stop playing that equip animation.
And then when our tool is activated, we want to go ahead and tell the server that we've swung our knife,
and that will let the server check if we hit anything and then damage that thing if we hit it.
So in order to play the animations on our player's character, we're going to have to load them using
the animator instance that is a child of the humanoid of our character.
So we need to grab our player's character.
We'll create a variable called character, and it's going to be equal to our player.
And then we'll get their character.
And just in case their character hasn't loaded or been replicated yet, then we're going to put an Or
statement here, and we're going to wait for the character added event to fire, which will return our
character.
And then once we get our character, we can go ahead and grab our humanoid, which is a child of our
character, and we're going to use wait for child humanoid.
And then we can also grab the animator as well, which is inside of our humanoid.
So we'll do humanoid wait for child animator.
Now, in order for our local script to communicate with our server script, we'll need to create a remote
event.
So inside of our knife, we can create a new remote event.
And I'm just going to call this my swung.
This is my swung event.
So whenever I click or activate the tool and I swing my knife, I can fire this event to the server
to let the server know, hey, I've activated my tool!
So we'll make a reference to our swung event.
A sequel to tool Dot swung.
And then we'll also get the properties for our tool as well, and we'll require from our tool and we'll
get properties.
Last but not least, we're also going to need a random data type to randomly pick which swing animation
we want to play when we activate our tool.
So I'll just call this variable RNG.
And that's going to be equal to a new random data type.
Now what we can go ahead and do is create a new private function for loading animation instances on
our players character.
So I'm going to call this function create track.
And we're going to get past an animation ID to this function.
And what I want this function to do is I want it to create a new animation instance.
So I'll have a variable called animation.
And that's going to be equal to instance dot new animation.
And then we want to go ahead and set the animations animation ID equal to the animation id passed to
this function.
And then from that point inside of the animator, we can use the load animation function and pass our
animation instance.
And that's going to return to us that track.
So we'll return it at the end of this function.
And that way we can go ahead and create a variable.
I'm going to call this my equip track.
And that's going to be equal to.
And we're going to use our create track function.
And inside of our properties we have our equip animation.
So we're going to load the equip animation on our player's character.
And that way when we equip our tool we can play this equip track.
And then when we unequip the tool we can stop playing this animation track.
Now when we go ahead and swing our tool, what we want to do is we want to play a random swinging animation.
So we'll get a swing track.
From our Create Track function, and the animation ID we want to pass is going to be a random one.
So inside of our properties we can get that swing animations table.
And then we're going to index it with a random number to grab a random animation.
So we're going to do RNG next integer one.
And then we're going to get the total amount of elements inside of our swing animations table.
So this will be from 1 to 3.
We'll get that random swing track and then we can play it.
And at the same time, we also may want to actually adjust the speed of our animation track.
Unfortunately, it's not auto filling these functions because it doesn't know what a swing track is.
So just in case, what I'm going to do is a little bit more type annotation, and I'm going to denote
that this function returns an animation track.
Again you do not need to worry about type annotation.
And this is not required for this function.
But just for the sake of this lecture to make it easier to follow along, I'm going to.
Type, annotate it.
That way we can get the auto fill and it tells us exactly what this animation track can do.
So for example, here we want to adjust the speed of the animation track.
And it says a positive value for speed plays the animation forward and negative one plays it backward
and zero pauses it.
So the speed we want to set it to is going to be equal to the value of one, which is the length of
our animation.
And we want to divide this value by our properties dot animation duration.
And that way we're setting the speed of our swing track to the correct speed to play it for the duration
defined in this property.
Then we can play the swing track, and then we're going to refer to our swing event, and we're going
to fire to the server that, hey, we have swung our tool.
And then we're going to wait for our swing track to stop playing.
So we'll wait for this event.
And once it's done playing, we can go ahead and destroy this swing track.
So that way we clean it up off of the character.
And then after the swing track is over, this is where we would want to wait for our swing cooldown.
So we could put a yield statement here and wait for the properties dot swing cooldown.
And that means to prevent the player from rapidly firing this event and executing this code multiple
times, we're going to need to create a Debounce variable.
So up here I'll create a variable.
I'll just call it swing Debounce set it to false by default.
But then when we activate our tool.
We'll set swing de bounce equal to true.
And before we do that, we're going to check if swing de bounce is already true.
And if it is, we're going to return.
Then we'll set it to true.
We'll wait for our cool down and then we'll set swing de bounce back to false.
Alrighty, we should now have all of the client stuff set up for our knife.
So when we activate our knife, we should play a random swinging animation.
And then when we equip and unequip it, the equip track should play and then stop playing.
So we'll go ahead and move our knife into the starter pack.
So that way our player can use it and then we'll play our game.
And then we'll click our play button and we'll spawn into our game, and we can go ahead and pull out
our knife.
There we are.
Our equip animation is playing.
And then if I click.
There you go.
As you can see, it is swinging our knife, but it appears that the animation is still slow.
So we may have to actually adjust the speed of our swing track after we start playing it.
So let's go ahead and try that instead.
I believe that our animation track actually has to be playing first before we are able to adjust the
speed.
So let's go ahead and try this again.
So if we spawn into our map.
And we get our knife right here.
If we click, there we go.
Now the speed of our swinging animations has been properly adjusted.
So now the next thing we need to do is implement the sounds for our knife, as well as listen for when
any humanoid in our game gets hit by our knife, and then we can damage them accordingly.
So if I put my knife back inside of my rig.
There is an invisible part inside of my knife called touch.
So let me make it opaque.
And this is the part where we're going to be listening for when anything touches this part, such as
a limb.
Then we're going to check what part.
Touch this part, and if that part belonged to a humanoid, and if it did, and that humanoid is not
another player, we're going to go ahead and damage them.
Now, of course, you can adjust the length of this part to be longer, to change or increase the range
of our knife when we swing it, but I'm going to leave it at this distance for now, because I think
that should be good enough for our knife.
So inside of our Nifs server scripts, we're going to go ahead and make the same reference to that event.
So I'll create a variable, I'll call it swung Event.
And before we can get the swing event we need to create a variable for our tool which is just script
dot parent.
Then we can refer to our tool and get the swung event.
And then we also want to make a reference to, for example, the touch part in our tool.
So tool dot touch.
And then we also want to grab the properties for our tool as well.
So we can require tool dot properties.
And then from this point we can go ahead and listen to our swung event.
Dot on server event.
Connect a function and we'll get the player that fired this event.
And something very important that we need to do is we need to make sure that whoever fired this event
is actually the player that has this tool equipped.
And the way we can do that is we can check if this player's name that fired this event, if it is not
equal to the tool's parent dot name, then we're going to return.
So the parent of this tool, when it is activated should be the player's character and the name of our
player's character and the name of the player instance should be the same.
Now, if an exploiter fired this event for another player, obviously the names are not going to match
up, so we're just going to ignore whoever fired this event because they're not the owner or the person
who has the tool equipped.
Otherwise.
Another important thing we need to do is we need to make sure we implement that same debounce that we
had on the client on the server as well, because any exploiter could easily bypass this client side
debounce and basically swing their knife super, super fast, which we don't want.
So we'll create a variable.
And I'm just going to call this variable swinging.
And it's just going to store a boolean.
We'll set it to false.
And then we're just going to do the exact same thing that we did in our local script.
If we are already swinging the knife we're going to return.
Otherwise we can set swinging equal to true.
And we'll actually do this after the first if statement.
And inside of here is where we will put all our code, and after that we'll set swinging back to false.
So what we want to do here is we want to go ahead and play the swinging sound, and then we want to
go ahead and check if we touched anything.
And if we did, we're going to damage that thing we touched and play the stabbing sound so we can go
ahead and create a function for creating a sound instance.
We'll call it create sound.
And here we can get past the parent for the sound instance as well as, um, the table of those sound
properties and the ID of the sound instance as well.
Then we can create a new sound instance.
So instance dot new sound.
And then with all these properties on the table, we can loop through, grab every property and their
value inside of our sound properties.
And then we can go ahead and update that property on our sound instance with the respective value.
So if you remember in our properties module script, we have a sound properties table with the name
of the property and the value we would like to update that property to.
Once we do that, we can go ahead and set the sound ID equal to the id passed to this function.
And now we can set the parent of the sound equal to the parent passed to the function, and then we'll
just return the sound instance.
We also want to create a function for creating that pitch shift.
So we'll call this function create pitch shift.
We'll get passed the parent that this pitch shift should belong to.
And then the value we should set for that octave property.
So we'll create a new pitch shift.
So instance dot new pitch shift sound effect.
And then inside of that we can go ahead and update the octave to be equal to that value.
We'll set the parent equal to the parent passed to the function, and then we can return this pitch
shift.
So now what we can do is we can go ahead and create a variable.
I'll call it swing sound.
And that's going to be equal to create sound.
The parent of the sound is going to be the handle of our tool.
So we can do tool dot handle.
The sound properties is going to be inside of our module script.
So sound properties.
And then the ID is going to be our properties dot swing sound ID.
Then we can go ahead and create the pitch shift for the sound as well.
So the parent is going to be our swing sound and the value is going to be a random value.
So we're going to need a random number generator.
So I'll create another RNG variable which is a new random data type.
And then we can do RNG next number because we are going to be picking a random decimal number.
And the minimum value is going to be our properties dot pitch shift octave dot minimum.
And then the maximum is going to be properties dot pitch shift octaves dot max.
Then we can go ahead and play this swing sound.
And then when this swing sound is finished playing.
So we'll listen to the end of the event, we'll connect a function, and then we're going to basically
destroy this swing sound because we don't need it anymore.
And we want to go ahead and clean up any instances that were no longer using.
Otherwise.
Once we're doing that, we want to go ahead and create a yield statement here and we're going to wait
for the properties dot swing cooldown.
And then we also want to wait for the duration of our animation or our swing animation.
So that's properties dot animation duration.
So now any time we swing and we fire this event, we're forcing a cooldown and the player is not going
to be able to execute this code again until this yield statement is up.
And then we set swinging back to false.
So let's go ahead and test what we have done so far.
We can move our knife back into.
The starter pack.
And we'll playtest our game and let's see if we get a swinging sound when we activate our knife.
So if I hold it and then click, there we go.
As you can see, we're now playing a swinging sound anytime we activate our knife.
Very cool.
Now, what we need to go ahead and do is detect when that part touches something, and then we can go
ahead and damage it.
So back inside of our server script, when we activate and we fire this event, what we can go ahead
and do is listen for when our touch part gets touched.
And we'll connect a function to this and get that part that touched the touch part in our tool.
And let's go ahead and create a dedicated function for this.
I'm going to call this function on touched, and we'll get past that part that touched us.
And then we also want to go ahead and grab the player.
As well, because when we damage a zombie, we want to go ahead and be able to keep track of whoever
last damaged a zombie.
And if we go to the zombie folder inside of server storage and we take a look at one of our zombies,
there is an object value inside each of our zombies called Who Last Damage.
And we're going to update the value in here equal to the player or whoever last damaged the zombie.
That way, when the zombie dies in the future, we can keep track of who killed what zombie and then
update like a kills value in the player's leaderboard.
So it's important that we get whoever attacked this zombie.
That way we can update the object values inside of them.
So when this function gets executed, we first want to make sure that we are swinging our knife.
So if we're not swinging the knife just in case, we're going to return.
Otherwise, we can go ahead and grab the humanoid, which is going to be equal to the other part dot
parent.
And we're going to find First Child, which is a humanoid.
So we want to make sure that the part we touched belongs to a character model or a humanoid model.
If there is not a humanoid, then we're just going to return.
Otherwise, we want to make sure that this humanoid does not belong to another player in our game.
So that means we're going to have to grab the player service.
And then what we could do is we want to use the players, get player from character function and pass
um, other part dot parent.
And if this function does return a player then we're just going to return.
Otherwise, what we can go ahead and do is since we've confirmed that we've hit a character model and
it's not a player, we can damage them.
And we also want to play that stabbing sound.
So I'm just going to copy what we did down here, paste that up here.
And instead I'm going to rename this using Control Shift and are going to rename this as my hit sound.
And instead we're going to pass the.
Hit sound ID.
Then we'll play the hit sound.
And afterwards, well, we can go ahead and do is calculate a critical chance.
So remember in our properties module script we have.
A critical hit chance.
So I can do is I'm going to create a variable called damage.
And it's going to be equal to my properties dot damage.
And then if by using our RNG data type and we use the next number function.
And the value is going to be between 0 and 1.
If the value returned by this function is less than or equal to our properties.
Dot chance or critical hit chance.
Then we can go ahead and update this damage value and multiply it by one of the, um, critical hit
multiplier values.
So since our critical hit chance is 0.15, that's a 15% chance.
So if any of the decimal numbers generated by this function is less than or equal to 0.15, then we
can go ahead and set damage.
And we're going to multiply this value by and we're going to use RNG.
Next number.
The minimum value is going to be properties dot critical hit multiplier dot min.
And then properties dot critical hit multiplier dot max.
So we've just updated the damage to reflect a new critical hit.
And afterwards we can get that humanoid that we grabbed up here.
And then there's a function called take damage for humanoids, and we can go ahead and pass the damage
that it's going to take away from its health.
Then we can check if this humanoid belongs to a zombie by seeing if the humanoid dot parent, which
would be the character model, has that object value called who last damage.
So if we find first child who last damaged, then that means this was a zombie we damaged and we can
go ahead and update the humanoid dot parent dot who last damaged and get the value.
And we're going to set that equal to the player that got passed to our function.
Now we can go ahead and call our on touch function pass other part as well as the player that has this
tool.
And something important for us to do is that we need to get the connection returned from our connect
function and store it in a variable.
That way, when our animation is over, we disconnect that connection to our touch part.
So that way we're not unnecessarily listening to when our tool is touched, when the player is not swinging
their knife around.
So I'm going to create a variable.
I'll call it touch connection.
And now we're going to delay a function from running, and we're going to delay it by the properties
dot animation duration.
So after their swinging animation is over we want to go ahead and disconnect our touch connection.
So we'll get touch connection and then disconnect it.
So that way we are no longer listening to our touch parts touch event.
And that way every single time this event is being fired, we're not unnecessarily connecting multiple
different functions to our same touch part.
So now we can go ahead and play our game and see if we are able to damage this player or this example,
a rig that is standing in our game.
So if we go and swing our knife, there we go.
We've damaged them and then perfect.
So now we are able to damage this, uh, rig that was in our game here.
I've placed multiple rigs down, and now if I go and attack them.
Well, it does seem like that we are hitting them multiple times with our knife, which we don't want
to happen, because if you heard the sound of our knife stab, it happened multiple times very quickly.
And that's probably because this touched event is being fired multiple times, because our knife is
going to be touching so many different limbs.
And because we're touching it multiple different times, it's going to be damaging our humanoid multiple
times, and it's going to be playing this hit sound multiple times.
So that means we should probably add a D bounce for our on touched function.
So that way we are not mistakenly damaging the humanoid many times in a second.
So what we can go ahead and do is create another boolean up here.
And I'm going to call this my touch Debounce.
And we're going to set it equal to false.
And then inside of our untouched function, we're going to check if touch debounce is true, and if
it is, we're going to return.
Otherwise we're going to set touch Debounce equal to true.
And then to prevent us from damaging something more than once, we're going to set touch debounce to
false down here instead.
So touch debounce equal to false.
That way we are guaranteed to only damage a zombie once every single time we swing our knife.
So now if we go and playtest our game.
And we grab our knife and we go to attack these rigs.
If I go and swing it, as you can see, I only damaged once instead of many times in that one single
swing of my knife.
Let me try it again.
There we go.
Only damaged once.
Let me see if I can try to get a critical hit.
No critical hit, but as you can see now we're only damaging our rigs once per swing.
So that's how we can create a melee weapon to damage any humanoid characters in our game.
See you in the next lecture.